/**
 *   Copyright 2014 Royce Remulla
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package cjcast.y2015.problem;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cjcast.common.AbstractSolution;


/**
 * Corner case: If a segment was never used, the final result cannot use them
 * too thus should be ERROR!.
 * 
 * 
 * @author royce.com@gmail.com
 */
//@Isolated(caseIndex = 7)
class SevensegmentDisplay extends AbstractSolution<SevenSegmentBean> {


    /** sl4j logger instance. */
    private static final Logger LOGGER = LoggerFactory
        .getLogger(SevensegmentDisplay.class);


    /** */
    static final int LED_FLAG_LEN = 7;

    /** Base of the digits, 10 since we are using decimal. */
    static final int DIGIT_LEN = Digit.values().length;


    /** */
    static final String ERROR_OUTPUT = "ERROR!";


    /** */
    static final int LED_ON = 1;

    /** */
    static final int LED_OFF = 0;

    /** */
    static final char LED_UNSURE = '\u0000';


    /** */
    static final char LED_GOOD = LED_ON;

    /** */
    static final char LED_BAD = LED_OFF;


    /**
     * If input is greater than the number of digits, simply return the existing
     * input on the remainder index.
     * 
     * {@inheritDoc}
     */
    @Override
    public String solve(final SevenSegmentBean caseBean)
    {
        final byte[] lEDSequence = caseBean.getLEDSequence();
        if (lEDSequence.length >= DIGIT_LEN) {
            return toBinaryString(lEDSequence[lEDSequence.length % DIGIT_LEN]); //NOPMD: Early return.
        }

        LOGGER.info(caseBean.toString());
        LEDPrinter.print(caseBean.getLedState());
        LEDPrinter.print(caseBean.getGoodLED());

        final List<Digit[]> listOfPossDigit = new ArrayList<Digit[]>();
        for (final byte nextLEDState : caseBean.getLEDSequence()) {

            final Digit[] potentialDigitArr = getPossibleDigits(
                caseBean.getGoodLED(),
                nextLEDState);

            if (potentialDigitArr.length == 0) {
                return ERROR_OUTPUT;
            } else {
                listOfPossDigit.add(potentialDigitArr);
            }
        }

        final String output = calcNextState(caseBean, listOfPossDigit);
        LEDPrinter.print(output);
        return output;
    }

    @SuppressWarnings("PMD.UseVarargs")
    String calcNextState(final SevenSegmentBean caseBean,
            final List<Digit[]> listOfPossDigit)
    {
        assert listOfPossDigit.size() == caseBean.getLEDSequence().length;

        final List<Digit[]> listOfValidDigit = new ArrayList<Digit[]>();

        for (final Digit nextDigit : listOfPossDigit.get(0)) {

            final Digit[] orderedDigit = findSequence(
                caseBean,
                nextDigit,
                listOfPossDigit);

            if (orderedDigit.length > 0) {
                listOfValidDigit.add(orderedDigit);
            }

        }

        String retval;
        final Digit[] uniqueDigits = listOfValidDigit.get(0);

        LOGGER.info("Unique Digits: " + Arrays.asList(uniqueDigits));


        final Digit lastDigit = uniqueDigits[uniqueDigits.length - 1];
        final Digit nextDigit = Digit.values()[(lastDigit.ordinal() + DIGIT_LEN - 1)
                % DIGIT_LEN];

        if ((nextDigit.toByte() & calculateUnknown(uniqueDigits)) != 0) {

            LOGGER.info("Unsure used.");
            retval = ERROR_OUTPUT;
        } else {

            retval = toBinaryString((byte) (nextDigit.breakLed(caseBean
                .getGoodLED()) & LEDState.LED_ALL_ON));
        }
        return retval;
    }


    /**
     * Calculate unused segments based on sequence of digits.
     * 
     * @return byte flag of segments that were never used.
     */
    byte calculateUnknown(final Digit[] digitSequence)
    {
        byte used = 0;
        for (final Digit digit : digitSequence) {
            used |= digit.toByte();
        }
        return (byte) ~used;
    }

    /**
     * Find the downward sequence if possible. Return null if it cannot be
     * determined.
     * 
     * @param nextDigit
     * @param listOfPossDigit
     * @return
     */
    @SuppressWarnings("PMD.OnlyOneReturn" /* Two only. */)
    Digit[] findSequence(final SevenSegmentBean caseBean,
            final Digit nextDigit, final List<Digit[]> listOfPossDigit)
    {
        Digit currentDigit = nextDigit;
        final List<Digit> goodSeqList = new ArrayList<Digit>();
        goodSeqList.add(nextDigit);

        for (int i = 1; i <= DIGIT_LEN && i < listOfPossDigit.size(); i++) {
            final List<Digit> nextDigitList = Arrays.asList(listOfPossDigit
                .get(i));

            currentDigit = Digit
                .fromValue((currentDigit.ordinal() - 1 + DIGIT_LEN) % DIGIT_LEN);
            assert currentDigit != null;

            if (nextDigitList.contains(currentDigit)) {
                goodSeqList.add(currentDigit);
            } else {
                break;

            }
        }

        if (goodSeqList.size() == listOfPossDigit.size()) {

            LOGGER.info(goodSeqList.toString());
            final Digit[] goodSeqArr = goodSeqList
                .toArray(new Digit[listOfPossDigit.size()]);

            return goodSeqArr;
        }
        return new Digit[0];

    }

    /**
     * Derives an array of all possible digits based on a give set of flags.
     * 
     * @param digitFlags flag of interest.
     */
    Digit[] getPossibleDigits(final byte goodLED, final byte actualLED)
    {
        final List<Digit> matchedDigitList = new ArrayList<Digit>();
        for (final Digit nextDigit : Digit.values()) {
            if (nextDigit.accept(actualLED, goodLED)) {
                matchedDigitList.add(nextDigit);
            }
        }

        return matchedDigitList.toArray(new Digit[matchedDigitList.size()]);
    }


    /** {@inheritDoc} */
    @Override
    protected int getCaseUnitSize()
    {
        return 1;
    }

    /** {@inheritDoc} */
    @Override
    protected SevenSegmentBean parseInputLines(final String... inputLineArr)
    {
        assert inputLineArr.length == 1;
        assert inputLineArr[0] != null;

        return new SevenSegmentBean(inputLineArr[0].substring(
            inputLineArr[0].indexOf(' ') + 1).split(" "));
    }


    String toBinaryString(final byte bite)
    {
        return String.format("%7s", Integer.toBinaryString(bite)).replace(
            ' ',
            '0');
    }

    /**
     * @param args unused.
     */
    public static void main(final String... args)
    {
        new SevensegmentDisplay().execute("c:\\");
    }

}


//98, 87
